/*
 * Created on Jul 16, 2008
 * Created by Paul Gardner
 * 
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License only.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package com.aelitis.azureus.core.lws;

import java.io.File;

import org.gudy.azureus2.core3.disk.DiskManager;
import org.gudy.azureus2.core3.disk.DiskManagerCheckRequest;
import org.gudy.azureus2.core3.disk.DiskManagerCheckRequestListener;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfo;
import org.gudy.azureus2.core3.disk.DiskManagerFileInfoSet;
import org.gudy.azureus2.core3.disk.DiskManagerListener;
import org.gudy.azureus2.core3.disk.DiskManagerPiece;
import org.gudy.azureus2.core3.disk.DiskManagerReadRequest;
import org.gudy.azureus2.core3.disk.DiskManagerReadRequestListener;
import org.gudy.azureus2.core3.disk.DiskManagerWriteRequest;
import org.gudy.azureus2.core3.disk.DiskManagerWriteRequestListener;
import org.gudy.azureus2.core3.disk.impl.DiskManagerFileInfoImpl;
import org.gudy.azureus2.core3.disk.impl.DiskManagerHelper;
import org.gudy.azureus2.core3.disk.impl.DiskManagerImpl;
import org.gudy.azureus2.core3.disk.impl.DiskManagerPieceImpl;
import org.gudy.azureus2.core3.disk.impl.DiskManagerRecheckScheduler;
import org.gudy.azureus2.core3.disk.impl.DiskManagerUtil;
import org.gudy.azureus2.core3.disk.impl.access.DMAccessFactory;
import org.gudy.azureus2.core3.disk.impl.access.DMChecker;
import org.gudy.azureus2.core3.disk.impl.access.DMReader;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceList;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMap;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapper;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapperFactory;
import org.gudy.azureus2.core3.disk.impl.piecemapper.DMPieceMapperFile;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.internat.LocaleTorrentUtil;
import org.gudy.azureus2.core3.internat.LocaleUtilDecoder;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.torrent.TOTorrentException;
import org.gudy.azureus2.core3.util.ByteFormatter;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.DirectByteBuffer;
import org.gudy.azureus2.core3.util.IndentWriter;

import com.aelitis.azureus.core.diskmanager.access.DiskAccessController;
import com.aelitis.azureus.core.diskmanager.cache.CacheFile;
import com.aelitis.azureus.core.diskmanager.cache.CacheFileOwner;

public class LWSDiskManager implements DiskManagerHelper {
    private static sePiece piece = new sePiece();

    private LightWeightSeed lws;
    private DiskAccessController disk_access_controller;
    private File save_file;
    private DMReader reader;
    private DMChecker checker_use_accessor;
    private DMPieceMapper piece_mapper;
    private DMPieceMap piece_map_use_accessor;

    private sePiece[] pieces;
    private DiskManagerFileInfoImpl[] files;
    private String internal_name;
    private DownloadManagerState download_state;

    private boolean started;
    private int state = DiskManager.INITIALIZING;
    private String error_message = "";

    protected LWSDiskManager(LightWeightSeed _lws, File _save_file) {
        lws = _lws;
        save_file = _save_file;

        disk_access_controller = DiskManagerImpl.getDefaultDiskAccessController();

        download_state = new LWSDiskManagerState();

        TOTorrent torrent = lws.getTOTorrent(false);

        pieces = new sePiece[torrent.getNumberOfPieces()];

        for (int i = 0; i < pieces.length; i++) {

            pieces[i] = piece;
        }
    }

    public String getName() {
        return (lws.getName());
    }

    public int getCacheMode() {
        return (CacheFileOwner.CACHE_MODE_NORMAL);
    }

    public long[] getReadStats() {
        if (reader == null) {

            return (new long[] { 0, 0 });
        }

        return (reader.getStats());
    }

    public void start() {
        try {
            TOTorrent torrent = lws.getTOTorrent(false);

            internal_name = ByteFormatter.nicePrint(torrent.getHash(), true);

            LocaleUtilDecoder locale_decoder = LocaleTorrentUtil.getTorrentEncoding(torrent);

            piece_mapper = DMPieceMapperFactory.create(torrent);

            piece_mapper.construct(locale_decoder, save_file.getName());

            files = getFileInfo(piece_mapper.getFiles(), save_file);

            reader = DMAccessFactory.createReader(this);

            reader.start();

            if (state != DiskManager.FAULTY) {

                started = true;

                state = DiskManager.READY;
            }
        } catch (Throwable e) {

            setFailed("start failed - " + Debug.getNestedExceptionMessage(e));
        }
    }

    protected DiskManagerFileInfoImpl[] getFileInfo(DMPieceMapperFile[] pm_files, File save_location) {
        boolean ok = false;

        DiskManagerFileInfoImpl[] local_files = new DiskManagerFileInfoImpl[pm_files.length];

        try {
            TOTorrent torrent = lws.getTOTorrent(false);

            if (torrent.isSimpleTorrent()) {

                save_location = save_location.getParentFile();
            }

            for (int i = 0; i < pm_files.length; i++) {

                DMPieceMapperFile pm_info = pm_files[i];

                File relative_file = pm_info.getDataFile();

                long target_length = pm_info.getLength();

                DiskManagerFileInfoImpl file_info =
                        new DiskManagerFileInfoImpl(this, save_location.toString(), relative_file, i, pm_info.getTorrentFile(),
                                DiskManagerFileInfo.ST_LINEAR);

                local_files[i] = file_info;

                CacheFile cache_file = file_info.getCacheFile();
                File data_file = file_info.getFile(true);

                if (!cache_file.exists()) {

                    throw (new Exception("File '" + data_file + "' doesn't exist"));
                }

                if (cache_file.getLength() != target_length) {

                    throw (new Exception("File '" + data_file + "' doesn't exist"));

                }

                pm_info.setFileInfo(file_info);
            }

            ok = true;

            return (local_files);

        } catch (Throwable e) {

            setFailed("getFiles failed - " + Debug.getNestedExceptionMessage(e));

            return (null);

        } finally {

            if (!ok) {

                for (int i = 0; i < local_files.length; i++) {

                    if (local_files[i] != null) {

                        local_files[i].close();
                    }
                }
            }
        }
    }

    public void setPieceDone(DiskManagerPieceImpl dmPiece, boolean done) {
    }

    public boolean stop(boolean closing) {
        started = false;

        if (reader != null) {

            reader.stop();

            reader = null;
        }

        if (files != null) {

            for (int i = 0; i < files.length; i++) {

                try {
                    files[i].getCacheFile().close();

                } catch (Throwable e) {

                    e.printStackTrace();
                }
            }
        }

        return (false);
    }

    public boolean isStopped() {
        return (!started);
    }

    public boolean filesExist() {
        throw (new RuntimeException("filesExist not implemented"));
    }

    public DiskManagerWriteRequest createWriteRequest(int pieceNumber, int offset, DirectByteBuffer data, Object user_data) {
        throw (new RuntimeException("createWriteRequest not implemented"));
    }

    public void enqueueWriteRequest(DiskManagerWriteRequest request, DiskManagerWriteRequestListener listener) {
        throw (new RuntimeException("enqueueWriteRequest not implemented"));
    }

    public boolean hasOutstandingWriteRequestForPiece(int piece_number) {
        throw (new RuntimeException("hasOutstandingWriteRequestForPiece not implemented"));
    }

    public boolean hasOutstandingReadRequestForPiece(int piece_number) {
        throw (new RuntimeException("hasOutstandingReadRequestForPiece not implemented"));
    }

    public boolean hasOutstandingCheckRequestForPiece(int piece_number) {
        throw (new RuntimeException("hasOutstandingCheckRequestForPiece not implemented"));
    }

    public DirectByteBuffer readBlock(int pieceNumber, int offset, int length) {
        return (reader.readBlock(pieceNumber, offset, length));
    }

    public DiskManagerReadRequest createReadRequest(int pieceNumber, int offset, int length) {
        return (reader.createReadRequest(pieceNumber, offset, length));
    }

    public void enqueueReadRequest(DiskManagerReadRequest request, DiskManagerReadRequestListener listener) {
        reader.readBlock(request, listener);
    }

    public DiskManagerCheckRequest createCheckRequest(int pieceNumber, Object user_data) {
        DMChecker checker = getChecker();

        return (checker.createCheckRequest(pieceNumber, user_data));
    }

    public void enqueueCheckRequest(DiskManagerCheckRequest request, DiskManagerCheckRequestListener listener) {
        DMChecker checker = getChecker();

        checker.enqueueCheckRequest(request, listener);
    }

    public void enqueueCompleteRecheckRequest(DiskManagerCheckRequest request, DiskManagerCheckRequestListener listener) {
        throw (new RuntimeException("enqueueCompleteRecheckRequest not implemented"));
    }

    public void setPieceCheckingEnabled(boolean enabled) {
    }

    public void saveResumeData(boolean interim_save) {
    }

    public DiskManagerPiece[] getPieces() {
        return (pieces);
    }

    public DiskManagerPiece getPiece(int index) {
        return (pieces[index]);
    }

    public boolean isInteresting(int piece_num) {
        return (false);
    }

    public boolean isDone(int piece_num) {
        return (false);
    }

    public int getNbPieces() {
        return (pieces.length);
    }

    public DiskManagerFileInfo[] getFiles() {
        return (files);
    }

    public DiskManagerFileInfoSet getFileSet() {
        throw (new RuntimeException("getFileSet not implemented"));
    }

    public int getState() {
        return (state);
    }

    public long getTotalLength() {
        return (piece_mapper.getTotalLength());
    }

    public int getPieceLength() {
        return (piece_mapper.getPieceLength());
    }

    public int getPieceLength(int piece_number) {
        if (piece_number == pieces.length - 1) {

            return (piece_mapper.getLastPieceLength());

        } else {

            return (piece_mapper.getPieceLength());
        }
    }

    public int getLastPieceLength() {
        return (piece_mapper.getLastPieceLength());
    }

    public long getRemaining() {
        return (0);
    }

    public long getRemainingExcludingDND() {
        return (0);
    }

    public int getPercentDone() {
        return (100);
    }

    public int getPercentDoneExcludingDND() {
        // Either this one or getPercentDone is wrong, mebbe
        return 1000;
    }

    public long getSizeExcludingDND() {
        return getTotalLength();
    }

    public String getErrorMessage() {
        return (error_message);
    }

    public void downloadEnded(OperationStatus op_status) {
    }

    public void moveDataFiles(File new_parent_dir, String new_name, OperationStatus op_status) {
        throw (new RuntimeException("moveDataFiles not implemented"));
    }

    public int getCompleteRecheckStatus() {
        return (-1);
    }

    public int getMoveProgress() {
        return (-1);
    }

    public boolean checkBlockConsistencyForWrite(String originator, int pieceNumber, int offset, DirectByteBuffer data) {
        long pos = pieceNumber * (long) piece_mapper.getPieceLength() + offset + data.remaining(DirectByteBuffer.AL_EXTERNAL);

        return (pos <= piece_mapper.getTotalLength());
    }

    public boolean checkBlockConsistencyForRead(String originator, boolean peer_request, int pieceNumber, int offset, int length) {
        return (DiskManagerUtil.checkBlockConsistencyForRead(this, originator, peer_request, pieceNumber, offset, length));
    }

    public boolean checkBlockConsistencyForHint(String originator, int pieceNumber, int offset, int length) {
        return (DiskManagerUtil.checkBlockConsistencyForHint(this, originator, pieceNumber, offset, length));
    }

    public void addListener(DiskManagerListener l) {

    }

    public void removeListener(DiskManagerListener l) {
    }

    public boolean hasListener(DiskManagerListener l) {
        return (false);
    }

    public void saveState() {
    }

    public DiskAccessController getDiskAccessController() {
        return (disk_access_controller);
    }

    public DMPieceMap getPieceMap() {
        DMPieceMap map = piece_map_use_accessor;

        if (map == null) {

            piece_map_use_accessor = map = piece_mapper.getPieceMap();
        }

        return (map);
    }

    public DMPieceList getPieceList(int piece_number) {
        DMPieceMap map = getPieceMap();

        return (map.getPieceList(piece_number));
    }

    protected DMChecker getChecker() {
        DMChecker checker = checker_use_accessor;

        if (checker == null) {

            checker = checker_use_accessor = DMAccessFactory.createChecker(this);
        }

        return (checker);
    }

    public byte[] getPieceHash(int piece_number)

    throws TOTorrentException {
        return (lws.getTorrent().getPieces()[piece_number]);
    }

    public DiskManagerRecheckScheduler getRecheckScheduler() {
        throw (new RuntimeException("getPieceHash not implemented"));
    }

    public void downloadRemoved() {
    }

    public void setFailed(String reason) {
        started = false;

        state = FAULTY;

        error_message = reason;
    }

    public void setFailed(DiskManagerFileInfo file, String reason) {
        started = false;

        state = FAULTY;

        error_message = reason;
    }

    public long getAllocated() {
        return (0);
    }

    public void setAllocated(long num) {
    }

    public void setPercentDone(int num) {
    }

    public TOTorrent getTorrent() {
        return (lws.getTOTorrent(false));
    }

    public String[] getStorageTypes() {
        throw (new RuntimeException("getStorageTypes not implemented"));
    }

    public String getStorageType(int fileIndex) {
        throw (new RuntimeException("getStorageType not implemented"));
    }

    public void accessModeChanged(DiskManagerFileInfoImpl file, int old_mode, int new_mode) {
    }

    public void skippedFileSetChanged(DiskManagerFileInfo file) {
    }

    public void priorityChanged(DiskManagerFileInfo file) {
    }

    public File getSaveLocation() {
        return (save_file);
    }

    public String getInternalName() {
        return (internal_name);
    }

    public DownloadManagerState getDownloadState() {
        return (download_state);
    }

    public void generateEvidence(IndentWriter writer) {
    }

    protected static class sePiece implements DiskManagerPiece {
        public void clearChecking() {
            throw (new RuntimeException("clearChecking not implemented"));
        }

        public boolean isNeedsCheck() {
            throw (new RuntimeException("isNeedsCheck not implemented"));
        }

        public int getLength() {
            throw (new RuntimeException("getLength not implemented"));
        }

        public int getNbBlocks() {
            throw (new RuntimeException("getNbBlocks not implemented"));
        }

        public int getPieceNumber() {
            throw (new RuntimeException("getPieceNumber not implemented"));
        }

        public int getBlockSize(int b) {
            throw (new RuntimeException("getBlockSize not implemented"));
        }

        public boolean isWritten() {
            throw (new RuntimeException("isWritten not implemented"));
        }

        public int getNbWritten() {
            throw (new RuntimeException("getNbWritten not implemented"));
        }

        public boolean[] getWritten() {
            throw (new RuntimeException("getWritten not implemented"));
        }

        public void reDownloadBlock(int blockNumber) {
            throw (new RuntimeException("reDownloadBlock not implemented"));
        }

        public void reset() {
            throw (new RuntimeException("reset not implemented"));
        }

        public boolean isDownloadable() {
            return (false);
        }

        public void setDownloadable() {
            throw (new RuntimeException("setRequestable not implemented"));
        }

        public DiskManager getManager() {
            throw (new RuntimeException("getManager not implemented"));
        }

        public boolean calcNeeded() {
            throw (new RuntimeException("calcNeeded not implemented"));
        }

        public void clearNeeded() {
            throw (new RuntimeException("clearNeeded not implemented"));
        }

        public boolean isNeeded() {
            throw (new RuntimeException("isNeeded not implemented"));
        }

        public void setNeeded() {
            throw (new RuntimeException("setNeeded not implemented"));
        }

        public void setNeeded(boolean b) {
            throw (new RuntimeException("setNeeded not implemented"));
        }

        public void setWritten(int b) {
            throw (new RuntimeException("setWritten not implemented"));
        }

        public boolean isWritten(int blockNumber) {
            throw (new RuntimeException("isWritten not implemented"));
        }

        public boolean calcChecking() {
            throw (new RuntimeException("calcChecking not implemented"));
        }

        public boolean isChecking() {
            return (false);
        }

        public void setChecking() {
            throw (new RuntimeException("setChecking not implemented"));
        }

        public void setChecking(boolean b) {
            throw (new RuntimeException("setChecking not implemented"));
        }

        public boolean calcDone() {
            throw (new RuntimeException("calcDone not implemented"));
        }

        public boolean isDone() {
            return (true);
        }

        public boolean isInteresting() {
            return (false);
        }

        public boolean isSkipped() {
            return false;
        }

        public String getString() {
            return ("");
        }

        public short getReadCount() {
            return 0;
        }

        public void setReadCount(short c) {
        }

        public void setDone(boolean b) {
            // get here when doing delayed rechecks

            if (!b) {

                Debug.out("Piece failed recheck");
            }

            // throw( new RuntimeException( "setDone not implemented" ));
        }
    }
}